package chan.project.smartlaweveryman.controller;

import chan.project.smartlawcommon.config.GetHttpSessionConfig;
import chan.project.smartlawmodel.dto.send.SendMessage;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.json.JSONUtil;

import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 用户上线请求
 */
@Component
@ServerEndpoint(value = "/channel/echo", configurator = GetHttpSessionConfig.class)
@Slf4j
public class CorrespondenceController implements ApplicationContextAware {

    private Session session;

    private static final String ONLINE_USER_LIST = "onlineUserList";

    private static final Map<String,Session> sessionMap = new ConcurrentHashMap<>();

    // 声明需要的 Bean
    private RedisTemplate redisTemplate;

    // 全局静态变量，保存 ApplicationContext
    private static ApplicationContext applicationContext;

    // 保存 Spring 注入的 ApplicationContext 到静态变量
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        CorrespondenceController.applicationContext = applicationContext;
    }

    // 收到消息
    @OnMessage
    public void onMessage(Session session,String message) throws IOException {
        //直接从请求头获取登录的token转化为当前用户
        String loginId = getUserId(session);

        //从参数中获取需要发送给的用户
        SendMessage sendMessage = JSONUtil.toBean(message, SendMessage.class);
        String sendMessages = sendMessage.getMessage();
        String sendUserId = sendMessage.getSendUserId();

        log.info("[websocket] 收到消息：userId={}，message={}", loginId, message);
        if (message.equalsIgnoreCase("bye")) {
            // 由服务器主动关闭连接。状态码为 NORMAL_CLOSURE（正常关闭）。
            session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "bye"));
            return;
        }
        //将收到的消息进行转发，转发给对应用户
        try {
            Session targetSession = sessionMap.get(sendUserId);
            // 转发消息给目标用户
            if (targetSession != null && targetSession.isOpen()) {
                targetSession.getBasicRemote().sendText(sendMessages);
                log.info("[websocket] 消息已转发给用户 {}", sendUserId);
                //将数据存储到数据库并标为已读
            }else {
                //将数据存储到数据库并标为未读
            }
        } catch (Exception e) {
            session.getBasicRemote().sendText("消息格式错误，请使用JSON格式：{'to':'目标ID', 'content':'内容'}");
        }
    }
    //todo 引入检测机制，将300秒内未发送消息的连接自动下线，以此优化连接

    // 连接打开
    @OnOpen
    public void onOpen(Session session, EndpointConfig endpointConfig){
        this.session = session;
        // 连接创建的时候，从 ApplicationContext 获取到 Bean 进行初始化
        this.redisTemplate = (RedisTemplate) CorrespondenceController.applicationContext.getBean("redisTemplate");
        Map<String, Object> userProperties = endpointConfig.getUserProperties();
        String token = (String) userProperties.get("satoken");
        String loginId = (String) StpUtil.getLoginIdByToken(token);
        //存储用户在线列表
        redisTemplate.opsForSet().add(ONLINE_USER_LIST, loginId);
        sessionMap.put(loginId,session);
        log.info("[websocket] 新的连接：userId={}", loginId);
    }

    // 连接关闭
    // 修改后的 onClose 方法
    @OnClose
    public void onClose(Session session,CloseReason closeReason){
        log.info("onClose进行调用");
        String loginId = getUserId(session);
        if(loginId != null && sessionMap.containsKey(loginId)){
            sessionMap.remove(loginId);
            redisTemplate.opsForSet().remove(ONLINE_USER_LIST, loginId);
        }
        log.info("[websocket] 连接断开：userId={}", loginId);
    }


    // 连接异常
    @OnError
    public void onError(Throwable throwable) throws IOException {
        log.info("[websocket] 连接异常：throwable={}",throwable.getMessage());
    }

    private String getUserId(Session session) {
        Map<String, Object> userProperties = session.getUserProperties();
        String token = (String) userProperties.get("satoken");
        String loginId = (String) StpUtil.getLoginIdByToken(token);
        return loginId;
    }
}